Casbinでアクセスコントロール
What is Casbin?
Casbinとは、ACL/RBAC/ABAC などのアクセス制御モデルをサポートするAuthorizationライブラリです。
Go/Java/Javascript/RustなどからDelphiやElixirまで幅広く対応しています。
Features of Casbin
公式ある説明より主要な機能の特徴。
- ハイブリッドアクセス制御モデル
Casbin では、アクセス制御モデルは PERM メタモデル (Policy, Effect, Request, Matchers) に基づいて
configファイルで設定できる。
なので、認可の仕組みを変更するのも簡単。
- 柔軟なポリシーストレージ
メモリやファイルだけでなく、ポリシー設定はMySQL、Postgres、OracleからMongoDB、Redis、
Cassandra、AWS S3などなどいろいろなデータストアがサポートされてる。
詳しくはここを確認。
- ロールマネージャ
ロールマネージャは、CasbinのRBACロール階層(ユーザ-ロールマッピング)を管理するために使用される。
ロールマネージャは、CasbinのポリシールールやLDAP、Okta、Auth0、Azure ADなどの外部ソースから
ロールデータを取得可能。
Casbinロールマネージャの詳細はここ。
Casbin's Model definition
CasbinではPERM-Metaモデル(Policy、Effect、Request、Matcher)でのアクセス制御モデルを採用しており、
モデル定義はconfファイルに記述する。
まずはACL(アクセスコントロールリスト)を例に説明。
- Request
アクセスする際のリクエストに関する情報。
ACLモデルでは一般的に、
sub(実行者)、obj(対象)、act(アクセス方法)
を使う。
[request_definition] r = {sub, obj, act}
- Policy
ポリシーの記述形式を定義するモデル。
例えばこのモデル定義で「data1 (obj) を alice (sub) が read (act) できる」
というPolicyを記述した場合、
p, alice, data1, read
となる。
ポリシーの記述形式は下記。
[policy_definition] p = {sub, obj, action}
- Matchers
RequestとPolicyがどうやってマッチするか、それぞれで定義したモデルを参照し、
条件式を使って定義する。
↓のACLモデル定義では、アクセス判定要求の
sub、obj、actの全てが一致するポリシーを合致するものと判定される。
[matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
- Effect
Requestが複数のポリシーにマッチした場合、どうやって判定させるか定義する。
※Matcherの評価値は、eftフィールドで利用可能
↓のACLモデル定義では、Matcherの条件式で合致するポリシーのうちeftが
allowに設定されたものが1つでもある場合に許可判定となる。
[policy_effect] e = some(where (p.eft == allow))
また、RBAC(ロールベールのアクセスコントロール)も使用可能。
ロールベースのアクセスコントロールとは、ユーザーなどに対してにロール(管理者などの役割)が
割当てられ、このロールに対して任意の機能に対する実行許可が与えられる。
これら以外にも、属性ベース(ABAC)やRESTfulの
アクセスコントロールについてもサポートしている。
Environment
今回はTypescriptでcasbinを動かしてみる。
- OS : MacOS 10.15.7
- node : v14.4.0
nodeはHomebrewでインストール済み。
Setup
まずはTypeScriptとcasbinをモジュールをインストール。
<br />% npm install --save casbin % npm install --save-dev @types/node % npm install --save-dev typescript # tsc --init しておく
Make Example
まずはcasbinのモデル定義用confファイルをbasic_mode.confという名前で作成する。 Request,Policy,Matchers,Effectの定義をそれぞれ記述。
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
次にポリシーファイルを用意。
aliceはdata1をreadでき、bobはdata2をwriteできます。
p, alice, data1, read p, bob, data2, write
サンプルコードを記述。
//example.ts const casbin = require('casbin'); //リソースへアクセスするユーザー const sub = 'alice'; //アクセス対象のリソース const obj = 'data1'; //リソースに対する操作 const act = 'read'; async function execute(){ const enforcer = await casbin.newEnforcer('basic_model.conf', 'basic_policy.csv'); const res = await enforcer.enforce(sub, obj, act); if (res) { // aliceのdata1に対するread操作は許可されている console.log("ok"); } else { // リクエストは拒否されている console.log("ng"); } } execute();
モデル定義ファイルとポリシーファイルを読み込み、操作が許可されているかどうかチェックする。
プログラムをコンパイルして実行すると、アクセスが許可されていることがわかる。
% tsc example.ts % node example.js ok
ロールモデルも試してみる。
まずはrole_model.confを定義。
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
role_definitionはRBAC形式に対応するための記述で、ロールの記述形式を定義している。
サンプルを記述。
taroに対してロール割当をおこなったあと、権限のチェックをする。
async function execute2() { const enforcer = await casbin.newEnforcer('role_model.conf', 'basic_policy.csv'); //ロール割り当て await enforcer.addNamedGroupingPolicy("g", "taro", "role1"); //ポリシー割当 await enforcer.addNamedPolicy("p", "role1", "data1", "read"); const res = await enforcer.enforce("taro", "data1", "read"); if (res) { console.log("role : ok"); } else { console.log("role : ng"); } //ユーザーのロール取得 const roles = await enforcer.getRolesForUser("taro"); console.log(roles); } execute2();
実行結果は以下。
% node example.js role : ok [ 'role1' ]
Summary
手軽にACLやRBAC形式のアクセス制御ができた。
ちなみに、fastify用のプラグインもあり、
簡単に組み込むこともできる。